/*
 * Board initialize code for Lemote YL8089.
 *
 * (C) Yanhua <yanh@lemote.com> 2009
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 */

#include <config.h>
#include <common.h>
#include <command.h>
#include <miiphy.h>
#include <netdev.h>
#include <pci.h>
#include <scsi.h>
#include <ahci.h>
#include <asm/io.h>
#include <asm/reboot.h>
#include <asm/gpio.h>

#include <asm/arch/ls2k.h>

DECLARE_GLOBAL_DATA_PTR;

#if defined(CONFIG_SHOW_BOOT_PROGRESS)
void show_boot_progress(int progress)
{
	printf("Boot reached stage %d\n", progress);
}
#endif

void _machine_restart(void)
{
	writel(1, LS2X_ACPI_REG(RST_CTR));

	while (1) {
		__asm__(".set push;\n"
			".set mips3;\n"
			"wait;\n"
			".set pop;\n"
		);
	}
}

phys_size_t initdram(int board_type)
{
	return get_ram_size ((long *)CONFIG_SYS_SDRAM_BASE, CONFIG_MEM_SIZE);
}

int checkboard(void)
{
	set_io_port_base(0x0);

	printf("checkboard\n");
	printf("Board: %s ", CONFIG_BOARD_NAME);
	printf("(CPU Speed %ld MHz/ Mem @ %ld MHz/ Bus @ %ld MHz)\n", gd->cpu_clk/1000000, gd->mem_clk/1000000, gd->bus_clk/1000000);

	return 0;
}

int incaip_set_cpuclk(void)
{
	u64 ls2x_refclk = OSC_CLK; //参考时钟固定为100MHz
	u64 ctrl = 0;
	u64 dc_clk, gmac_clk;
	unsigned int l1div_out, l1div_loopc, l1div_ref;
	unsigned int l2div_out;
	unsigned int mult, div;

	/* node cpu clk */
	ctrl = (u64)readl(LS2X_NODE_PLL_L);
	ctrl = ctrl | ((u64)readl(LS2X_NODE_PLL_L + 4) << 32);
	l1div_out = (ctrl >> NODE_L1DIV_OUT_SHIFT) & NODE_L1DIV_OUT_MARK;
	l1div_loopc = (ctrl >> NODE_L1DIV_LOOPC_SHIFT) & NODE_L1DIV_LOOPC_MARK;
	l1div_ref = (ctrl >> NODE_L1DIV_REF_SHIFT) & NODE_L1DIV_REF_MARK;
	ctrl = (u64)readl(LS2X_NODE_PLL_H);
	ctrl = ctrl | ((u64)readl(LS2X_NODE_PLL_H + 4) << 32);
	l2div_out = (ctrl >> NODE_L2DIV_OUT_SHIFT) & NODE_L2DIV_OUT_MARK;
	mult = l1div_loopc;
	div = l1div_ref * l1div_out * l2div_out;
	gd->cpu_clk = ls2x_refclk * mult / div;

	/* ddr gpu hda clk */
	ctrl = (u64)readl(LS2X_DDR_PLL_L);
	ctrl = ctrl | ((u64)readl(LS2X_DDR_PLL_L + 4) << 32);
	l1div_out = (ctrl >> DDR_L1DIV_OUT_SHIFT) & DDR_L1DIV_OUT_MARK;
	l1div_loopc = (ctrl >> DDR_L1DIV_LOOPC_SHIFT) & DDR_L1DIV_LOOPC_MARK;
	l1div_ref = (ctrl >> DDR_L1DIV_REF_SHIFT) & DDR_L1DIV_REF_MARK;
	ctrl = (u64)readl(LS2X_DDR_PLL_H);
	ctrl = ctrl | ((u64)readl(LS2X_DDR_PLL_H + 4) << 32);
	l2div_out = (ctrl >> DDR_L2DIV_OUT_DDR_SHIFT) & DDR_L2DIV_OUT_DDR_MARK;
	mult = l1div_loopc;
	div = l1div_ref * l1div_out * l2div_out;
	gd->mem_clk = ls2x_refclk * mult / div;

	/* dc gmac clk */
	ctrl = (u64)readl(LS2X_DC_PLL_L);
	ctrl = ctrl | ((u64)readl(LS2X_DC_PLL_L + 4) << 32);
	l1div_out = (ctrl >> DC_L1DIV_OUT_SHIFT) & DC_L1DIV_OUT_MARK;
	l1div_loopc = (ctrl >> DC_L1DIV_LOOPC_SHIFT) & DC_L1DIV_LOOPC_MARK;
	l1div_ref = (ctrl >> DC_L1DIV_REF_SHIFT) & DC_L1DIV_REF_MARK;
	ctrl = (u64)readl(LS2X_DC_PLL_H);
	ctrl = ctrl | ((u64)readl(LS2X_DC_PLL_H + 4) << 32);
	l2div_out = (ctrl >> DC_L2DIV_OUT_DC_SHIFT) & DC_L2DIV_OUT_DC_MARK;
	mult = l1div_loopc;
	div = l1div_ref * l1div_out * l2div_out;
	dc_clk = ls2x_refclk * mult / div;

	l2div_out = (ctrl >> DC_L2DIV_OUT_GMAC_SHIFT) & DC_L2DIV_OUT_GMAC_MARK;
	div = l1div_ref * l1div_out * l2div_out;
	gmac_clk = ls2x_refclk * mult / div;

	/* apb usb sata clk */
	ctrl = (u64)readl(LS2X_FREQ_SCALE);
	ctrl = ctrl | ((u64)readl(LS2X_FREQ_SCALE + 4) << 32);
	{
	unsigned int apb_freqscale;
	apb_freqscale = (ctrl >> FREQSCALE_APB_SHIFT) & FREQSCALE_APB_MARK;
	gd->bus_clk = gmac_clk * 1 / (8/(apb_freqscale+1));
	}

	gd->arch.pll_clk = ls2x_refclk;

	return 0;
}

#ifndef CONFIG_NAND_LS2K_DMACHAN
#define CONFIG_NAND_LS2K_DMACHAN 0
#endif
#ifndef CONFIG_SDIO_LS2K_DMACHAN
#define CONFIG_SDIO_LS2K_DMACHAN 4
#endif

/* 设置设备使用的apb dma通道 */
static void ls2x_set_apbdma(void)
{
	u32 val;

	val = (CONFIG_NAND_LS2K_DMACHAN<<0) |
		(1<<3) | (2<<6) | (1<<9) | (2<<12) | (CONFIG_SDIO_LS2K_DMACHAN<<15) | (1<<18) | (3<<21);
	__raw_writel(val, CKSEG1ADDR(LS2X_APB_DMA_CFG));
}

int board_early_init_f(void)
{
	ls2x_set_apbdma();

	return 0;
}

#ifdef CONFIG_ARCH_EARLY_INIT_R
extern char wait_for_smp_call(void);

int arch_early_init_r(void)
{
	//core1 run wait_for_smp_call function in ram
	asm volatile(".set mips64;sd %1,(%0);.set mips3;"::"r"(0xbfe11120),"r"(&wait_for_smp_call));

	return 0;
}
#endif

int power_init_board(void)
{
	u32 val = 0;

	//设置i2c gpio复用
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
	val = readl(CKSEG1ADDR(LS2X_MUX_BASE));
#if defined(CONFIG_LS1X_I2C0)
	val = val | (1<<10);
#endif
#if defined(CONFIG_LS1X_I2C1)
	val = val | (1<<11);
#endif
	writel(val, CKSEG1ADDR(LS2X_MUX_BASE));
#endif

#if defined(CONFIG_BOARD_LS2K_PAI)
	//设置gpio sdio复用
	val = readl(CKSEG1ADDR(LS2X_MUX_BASE));
	val = val & (~(1<<20));
	writel(val, CKSEG1ADDR(LS2X_MUX_BASE));
	//gpio36用作phy复位引脚
	gpio_direction_output(36, 0);
	udelay(1000);
	gpio_direction_output(36, 1);
#endif

	return val;
}

#ifdef CONFIG_LS2X_GMAC
extern s32 gmac_initialize(char *dev_name, void *base_addr);

int board_eth_init(bd_t *bis)
{
	char *name = "syn0";
	pci_dev_t busdevfunc;
	void *gmac_base;

	busdevfunc = pci_find_device(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_GMAC, 0); /* get PCI Device ID */
	if (busdevfunc == -1) {
		printf("Gmac Controller (%04X,%04X) not found\n", PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_GMAC);
		return 0;
	}
	gmac_base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);

	return gmac_initialize(name, gmac_base);
}
#endif

unsigned long long memorysize;
unsigned long long memorysize_high;
#define INTPOL1_REG 0xbfd00068
extern int tlb_init_default(void);

int misc_init_f(void)
{
#ifdef ARB_LEVEL
	save_board_ddrparam(0);
#endif
 return 0;
}

int misc_init_r(void)
{
	register volatile int raw_memsz asm ("k1");
	unsigned long long memsz;
	char env[512];

#ifdef CONFIG_SCSI_AHCI
#ifdef CONFIG_SCSI_AHCI_PLAT
	u32 val = 0;
	pci_dev_t busdevfunc;
	void *ahci_base;

	/* Config SATA */
	val = 0x30c31cf9;
	writel(val, CKSEG1ADDR(0x1fe10454));
	val = 0xf300040f;
	writel(val, CKSEG1ADDR(0x1fe10450));

	/* Config SATA : use internel clock */
	val = readl(CKSEG1ADDR(0x1fe10450));
	val = val & (~(1<<1));
	val = val | (1<<3) | (1<<2);
	writel(val, CKSEG1ADDR(0x1fe10450));

	busdevfunc = pci_find_device(SCSI_VEND_ID, SCSI_DEV_ID, 0); /* get PCI Device ID */
	if (busdevfunc == -1) {
		printf("SCSI Controller (%04X,%04X) not found\n", SCSI_VEND_ID, SCSI_DEV_ID);
		return 0;
	}
	ahci_base = pci_map_bar(busdevfunc, 0x10, PCI_REGION_MEM);
	if(!ahci_init((u32)ahci_base))
		scsi_scan(1);
#else
	puts("SCSI:  ");
	scsi_init();
#endif
#endif

	memsz = raw_memsz & 0xff;
	memsz = memsz << 29;
	memsz = memsz - 0x1000000;
	memsz = memsz >> 20;
	printf("raw_memsz 0x%llx memsz %lld\n", raw_memsz, memsz);

	memorysize = memsz > 240 ? 240 << 20 : memsz << 20;
	memorysize_high = memsz > 240 ? (memsz - 240) << 20 : 0;

	sprintf(env, "%lld", memorysize / (1024 * 1024));
	setenv ("memsize", env);

	sprintf(env, "%lld", memorysize_high / (1024 * 1024));
	setenv ("highmemsize", env);

	/*disable spi instruct fetch before enter spi io mode*/
	if (*(volatile int *)0xbfe10080 == 0x1fc000f2) {
		*(volatile int *)0xbfe10080 = 0x1fc00082;
	}

	tlb_init_default();

	return 0;
}

#if defined(CONFIG_CMD_MMC) && defined(CONFIG_LS1X_MMC)
extern int ls1x_mmc_register(int card_index, int cd_gpio,
		int wp_gpio);

int board_mmc_init(bd_t *bis)
{
	ls1x_mmc_register(0, CONFIG_LS1X_MMC_CD, CONFIG_LS1X_MMC_WP);
	return 0;
}
#endif
